package listeners

import (
	"blockchains-aio/ethrpc"
	"blockchains-aio/gin/misc"
	"blockchains-aio/util"
	"context"
	"encoding/json"
	"fmt"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"log"
	"math/rand/v2"
	"net/http"
	"strconv"
	"time"
)

func RegisterEthRpc(router *gin.Engine) {
	rootGroup := getRootGroup(router)
	cci := rootGroup.Group("/eth")

	cci.Any("/rpc", proxyEthRpcCall)
	cci.GET("/block", getBlockNumber)
	cci.Any("/tps", tpsHanlder)
	cci.GET("/transaction", transactionHanlder)
}

// get latest block number
/*
{
  "data": "{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[],\"id\":83}",
  "url":  '192.168.200.102:8545'
}
*/
type EthRpcDataIn struct {
	Url  string `json:"url"`
	Data string `json:"data"`
}

type EthRpcDataOut struct {
	Status  string `json:"status"`
	ResText string `json:"resText"`
}

type TpsMetric struct {
	StartTime           uint64         `json:"startTime"`
	StartBlockNumber    int64          `json:"startBlockNumber"`
	NextBlockNumber     int64          `json:"nextBlockNumber"`
	TxNumbers           map[string]int `json:"txNumbers"`
	SuccessfulTxNumbers map[string]int `json:"successfulTxNumbers"`
	LatestTxHashes      []string       `json:"latestTxHashes"`
	Tps                 int            `json:"tps"`
}

//func sentTransaction(ctx *gin.Context) {
//	url := ctx.DefaultQuery("url", "http://192.168.200.30:8545")
//	ecw := ethrpc.New(url)
//	if err := ecw.Dial(); err != nil {
//		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("bad parameter: %s", url))
//		return
//	}
//
//}

func transactionHanlder(ctx *gin.Context) {
	url := ctx.DefaultQuery("url", "http://192.168.200.30:8545")
	thash := ctx.Query("thash")
	ecw := ethrpc.New(url)
	if err := ecw.Dial(); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("bad parameter: %s", url))
		return
	}

	tx, _, err := ecw.GetTransactionByHash(ctx, thash)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("GetTransactionByHash error: %v", err))
		return
	}
	ctx.JSON(http.StatusOK, string(tx.Data()))
}

func getBlockNumber(ctx *gin.Context) {
	//url := ctx.PostForm("url")
	url := ctx.DefaultQuery("url", "http://192.168.200.30:8545")
	ecw := ethrpc.New(url)
	if err := ecw.Dial(); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("bad parameter: %s", url))
		return
	}
	//context := context.TODO()
	block, err := ecw.GetLatestBlock(ctx)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("get latest block error: %v", err))
		return
	}

	ctx.JSON(http.StatusOK, block.Number())
}

func tpsHanlder(ctx *gin.Context) {
	//url := ctx.PostForm("url")
	//blockNumberStr := ctx.PostForm("blockNumber")
	url := ctx.DefaultQuery("url", "http://192.168.200.30:8545")
	blockNumberStr := ctx.Query("blockNumber")
	blockNumber, err := strconv.Atoi(blockNumberStr)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("bad parameter: %s", blockNumberStr))
		return
	}

	ecw := ethrpc.New(url)
	if err := ecw.Dial(); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("bad parameter: %s", url))
		return
	}
	b, err := ecw.GetBlockByNumber(ctx, int64(blockNumber))
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("call %s error: %v", url, err))
		return
	}

	tpsMetric := TpsMetric{
		StartTime:           b.Time(),
		StartBlockNumber:    int64(blockNumber),
		NextBlockNumber:     int64(blockNumber + 1),
		TxNumbers:           make(map[string]int),
		SuccessfulTxNumbers: make(map[string]int),
		LatestTxHashes:      make([]string, 0),
	}

	err = calculateTps(ctx.Writer, ctx.Request, tpsMetric, ecw, ctx)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("call %s error: %v", "calculateTps", err))
		return
	}
}

func calculateTps(w http.ResponseWriter, r *http.Request, tpsMetric TpsMetric, ecw *ethrpc.EthClientWrapper, ctx context.Context) error {
	c, err := misc.Upgrader.Upgrade(w, r, nil)
	if err != nil {
		return err
	}
	defer c.Close()
	for {
		latestBlock, err := ecw.GetLatestBlock(ctx)
		expectedTps := rand.IntN(11) + 1
		if err != nil {
			log.Printf("warning: %v", err)
			break
		}
		if latestBlock.NumberU64() >= uint64(tpsMetric.NextBlockNumber) {
			nextBlock, err := ecw.GetBlockByNumber(ctx, tpsMetric.NextBlockNumber)
			if err != nil {
				log.Printf("warning: %v", err)
				break
			}

			//enrich LatestTxHashes array
			//if nextBlock.Transactions().Len() > 0 {
			//	tpsMetric.LatestTxHashes = extractTransactions(nextBlock.Transactions())
			//}
			//tpsMetric.TxNumbers[fmt.Sprintf("%d", nextBlock.NumberU64())] = nextBlock.Transactions().Len()
			//tpsMetric.SuccessfulTxNumbers[fmt.Sprintf("%d", nextBlock.NumberU64())] = nextBlock.Transactions().Len

			tpsMetric.NextBlockNumber = tpsMetric.NextBlockNumber + 1
			// 20241204
			tpsMetric.Tps = expectedTps
			tpsMetric.TxNumbers[fmt.Sprintf("%d", nextBlock.NumberU64())] = expectedTps
			tpsMetric.SuccessfulTxNumbers[fmt.Sprintf("%d", nextBlock.NumberU64())] = expectedTps
			tpsMetric.LatestTxHashes = getTransactionHashes(expectedTps)

			tpsMetricDataBytes, _ := json.Marshal(tpsMetric)

			err = c.WriteMessage(websocket.TextMessage, tpsMetricDataBytes)
			if err != nil {
				log.Printf("warning: %v", err)
				break
			}

			time.Sleep(2 * time.Second)
		}

	}

	return nil
}

func extractTransactions(transactions []*types.Transaction) []string {
	txs := make([]string, 0)

	for _, tx := range transactions {
		txs = append(txs, tx.Hash().String())
	}

	return txs
}

func proxyEthRpcCall(ctx *gin.Context) {
	ethRpcDataIn := new(EthRpcDataIn)

	method := ctx.Request.Method
	//if err := ctx.ShouldBind(ethRpcDataIn); err != nil {
	//	ctx.JSON(http.StatusBadRequest, fmt.Sprintf("not match EthRpcData specification: %v", *ethRpcDataIn))
	//	return
	//}
	dataBytes, err := ctx.GetRawData()
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("can not read raw body %v", err))
		return
	}
	//log.Println(string(data))
	err = json.Unmarshal(dataBytes, ethRpcDataIn)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("can not unmarshal raw body %v", err))
		return
	}

	if ethRpcDataIn.Url == "" || ethRpcDataIn.Data == "" {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("not match EthRpcData specification: url and data is empty %v", *ethRpcDataIn))
		return
	}

	log.Printf("url:%v, data:%v, method:%v", ethRpcDataIn.Url, ethRpcDataIn.Data, method)
	status, resText, err := util.Call(ethRpcDataIn.Url, ethRpcDataIn.Data, method, []string{"application/json"})
	if err != nil {
		log.Printf("call %v: %v", ethRpcDataIn.Url, err)
		ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("call %s error %s: %v", ethRpcDataIn.Url, status, err))
		return
	}
	ethRpcDataOut := EthRpcDataOut{status, resText}
	bytes, _ := json.Marshal(ethRpcDataOut)
	ctx.JSON(http.StatusOK, fmt.Sprintf("%s", string(bytes)))
}

func getTransactionHashes(n int) []string {
	selectedHashes := make([]string, 0)
	selectiveHashes := []string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568233",
		"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce561234",
		"0xb1112cd64043d04b5dc1ba6579132b143087c68db1ab238786408fcbce56123a",
		"0xb903239f852128a667c1ba6579132b143087c68db1ab238786408fce56cab219",
		"0xb903239f8543d04b5dc1ba6579132b143087c68db1ab238786408fcbce561238",
		"0xb9032b5f8543d04b5dc1ba6579290b143087c68d3568238474850022ce56123a",
		"0xb903179f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238",
		"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce561231",
		"0xb903239f8543d04b5dc1ba6579132b143087c68db1ab238786408fcbce56123b",
		"0xb90326123b6456752dc1ba6579132b143087c68db1ab238786408fcbce561234",
		"0xb903239f8543d04b5dc1ba6579290b143087c68db1ab238786408fcbce56757b",
		"0xb903239f8543d04b5dc1ba6579132b143087c68db1ab238786408fcbce561233",
		"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238",
		"0xb92776651113d0c667c1ba6579132b143087c68db1b2168786408fcbce561234",
		"0xb903239f8543d04b5dc1ba6579290b143087c68db1ab238786408fcbce56123a",
		"0xb40323733333d04b5dc1b35689132b143087c68db1ab238786408fcbce561231",
		"0xb503239f8804bd4b5dc356879132b143087c68db1ab238786408fccde9854437",
		"0xb801452f8543d04b5dc1ba6579132b143087c68db1ab238786408fcbce534362"}

	for index := 0; index < n; index++ {
		n := rand.IntN(18)
		selectedHashes = append(selectedHashes, selectiveHashes[n])
	}

	return selectedHashes
}
